
class Face :
	def __init__(self, a=0, b=0, c=0, u=0.0, v=0.0, w=0.0) :
		self.a = int(a)
		self.b = int(b)
		self.c = int(c)
		self.u = float(u)
		self.v = float(v)
		self.w = float(w)
		
	def get(self, x) :
		if x == "a" :
			return self.a
		if x == "b" :
			return self.b
		if x == "c" :
			return self.c
		if x == "u" :
			return self.u
		if x == "v" :
			return self.v
		if x == "w" :
			return self.w
			
	def normal_for_axis(self, axis) :
		if axis == 'x' : return self.u
		if axis == 'y' : return self.v
		return self.w
			
	def __str__(self) :
		return "(%d, %d, %d)" % (self.a, self.b, self.c)
		return "(%d, %d, %d) - (%f, %f, %f)" % (self.a, self.b, self.c, self.u, self.v, self.w)
	
class Vertex :
	def __init__(self, x=0.0, y=0.0, z=0.0, u=0.0, v=0.0, w=0.0) :
		self.x = float(x)
		self.y = float(y)
		self.z = float(z)
		self.u = float(u)
		self.v = float(v)
		self.w = float(w)

	def __getattr__(self, attr) :
		if attr == "xyz" :
			return (self.x, self.y, self.z)
		raise"Attribute Error"
		
	def get(self, x) :
		if x == "x" :
			return self.x
		if x == "y" :
			return self.y
		if x == "z" :
			return self.z
		if x == "u" :
			return self.u
		if x == "v" :
			return self.v
		if x == "w" :
			return self.w
			
	def set(self, x, v) :
		if x == "x" :
			self.x = v
		if x == "y" :
			self.y = v
		if x == "z" :
			self.z = v
		if x == "u" :
			self.u = v
		if x == "v" :
			self.v = v
		if x == "w" :
			self.w = v
			
			
			
	def __str__(self) :
		return "(%f, %f, %f)" % (self.x, self.y, self.z)
		return "(%f, %f, %f) - (%f, %f, %f)" % (self.x, self.y, self.z, self.u, self.v, self.w)
		
	def scale(self, s) :
		self.x *= s
		self.y *= s
		self.z *= s
		
	def half_way_toward(self, target) :
		return Vertex(
			self.x + 0.5 * (target.x - self.x), 
			self.y + 0.5 * (target.y - self.y), 
			self.z + 0.5 * (target.z - self.z), 
			(self.u + target.u)/2, 
			(self.v + target.v)/2, 
			(self.w + target.w)/2
		)		
	
		
class Slope : # http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#line_line_intersection
	def __init__(self, x1, y1, x2, y2) :
		self.a = y2 - y1
		self.b = x1 - x2
		self.c = self.a * x1 + self.b * y1
		
	def intersect(self, s) :
		det = self.a * s.b - s.a * self.b
		
		x = (s.b * self.c - self.b * s.c) / det
		y =  (self.a * s.c - s.a * self.c) / det
		
		return (x, y)		

class Mesh :
	vertex = {}
	face = {}
	max_v = 0
	max_f = 0
	
	def __init__(self, fname=None) :
		if fname :
			self.read_from_ase(fname)
	
	def add_vertex(self, v) :
		self.max_v += 1
		self.vertex[self.max_v] = v
		return self.max_v
		
	def add_face(self, f) :
		self.max_f += 1
		self.face[self.max_f] = f
		return self.max_f
					
	def read_from_ase(self, fname) :
		for l in open(fname, 'r').readlines() :
			b = l.strip().split()
			if b[0] == "*MESH_VERTEX" :
				i = int(b[1])
				if i > self.max_v :
					self.max_v = i
					
				self.vertex[i] = Vertex(b[2], b[3], b[4])
				
			elif b[0] == "*MESH_FACE" :
				i = int(b[1].strip(":"))
				if i > self.max_f :
					self.max_f = i
				self.face[i] = Face(b[3], b[5], b[7])
			elif b[0] == "*MESH_FACENORMAL" :
				self.face[int(b[1])].u = float(b[2])
				self.face[int(b[1])].v = float(b[3])
				self.face[int(b[1])].w = float(b[4])
			elif b[0] == "*MESH_VERTEXNORMAL" :
				self.vertex[int(b[1])].u = float(b[2])
				self.vertex[int(b[1])].v = float(b[3])
				self.vertex[int(b[1])].w = float(b[4])

	def write_to_DXF(self, dxfe, dxf) :
		for f in self.face :
			f = self.face[f]
			dxf.add(dxfe.line(self.vertex[f.a].xyz, self.vertex[f.b].xyz))
			dxf.add(dxfe.line(self.vertex[f.b].xyz, self.vertex[f.c].xyz))
			dxf.add(dxfe.line(self.vertex[f.c].xyz, self.vertex[f.a].xyz))
			
			
	def bounding_box(self) :
		xmin = self.vertex[0].x
		xmax = self.vertex[0].x
		ymin = self.vertex[0].y
		ymax = self.vertex[0].y
		zmin = self.vertex[0].z
		zmax = self.vertex[0].z
				
		for v in self.vertex :
			v = self.vertex[v]
			if v.x < xmin:
				xmin = v.x
			elif v.x > xmax:
				xmax = v.x
			if v.y < ymin:
				ymin = v.y
			elif v.y > ymax:
				ymax = v.y
			if v.z < zmin:
				zmin = v.z
			elif v.z > zmax:
				zmax = v.z
				
		return ((xmin, ymin, zmin), (xmax, ymax, zmax))
		
	def vertices_on_bbox(self) :
		bbox = self.bounding_box()
		
		for vk in self.vertex :
			v = self.vertex[vk]
			
			if v.x == bbox[0][0] or v.x == bbox[1][0] :
				yield vk
			elif v.y == bbox[0][1] or v.y == bbox[1][1] :
				yield vk
			elif v.z == bbox[0][2] or v.z == bbox[1][2] :
				yield vk
	
	def delete_faces_with_vertices_on_box(self) :
		vertices = [v for v in self.vertices_on_bbox()]
		def flat(u, v, w) :
			k = 0
			if abs(u) > 0 :
				k += 1
			if abs(v) > 0 :
				k += 1
			if abs(w) > 0 :
				k += 1
			return k == 1
		def all_on_bbox(g) :
			 return g.a in vertices and g.b in vertices and g.c in vertices

		self.delete_faces([fk for fk in self.face if all_on_bbox(self.face[fk]) and flat(self.face[fk].u, self.face[fk].v, self.face[fk].w)])
		
	def delete_faces(self, faces) :
		for k in faces :
			del self.face[k]

	def delete_unused_vertices(self) :
		for k in [vk for vk in self.vertex if len(self.faces_of_vertex(vk)) == 0] :
			del self.vertex[k]
		
	def delete_faces_with_minus_axis_normal(self, axis) :
		self.delete_faces([fk for fk in self.face if self.face[fk].get(axis) < 0])
		
	def delete_faces_with_positive_axis_normal(self, axis) :
		self.delete_faces([fk for fk in self.face if self.face[fk].get(axis) > 0])
	
	def faces_of_vertex(self, vk) :
		return [fk for fk in self.face if self.face[fk].a == vk or self.face[fk].b == vk or  self.face[fk].c == vk]

	def project_to_zmax(self) :
		((xmin, ymin, zmin), (xmax, ymax, zmax)) = self.bounding_box()
		for vk in self.vertex :
			self.vertex[vk].z = zmax

	def project_to_xmax(self) :
		((xmin, ymin, zmin), (xmax, ymax, zmax)) = self.bounding_box()
		for vk in self.vertex :
			self.vertex[vk].x = xmax

	def project_to_ymax(self) :
		((xmin, ymin, zmin), (xmax, ymax, zmax)) = self.bounding_box()
		for vk in self.vertex :
			self.vertex[vk].y = ymax


	def delete_faces_below_y(self, y) :
		
		self.delete_faces([fk for fk in self.face if self.vertex[self.face[fk].a].y < y and self.vertex[self.face[fk].b].y < y  and self.vertex[self.face[fk].c].y < y])
	
	def delete_faces_above_y(self, y) :
		
		self.delete_faces([fk for fk in self.face if self.vertex[self.face[fk].a].y > y and self.vertex[self.face[fk].b].y > y  and self.vertex[self.face[fk].c].y > y])

	def faces_crossing_axis(self, axis, at_value) :
		faces = []
		for fk in self.face :
			above_axis = {'a':0,'b':0,'c':0}
			
			def above(c1, c2) :
				v1 = self.vertex[self.face[fk].get(c1)].get(axis)
				v2 = self.vertex[self.face[fk].get(c2)].get(axis)
				if  v1 <  at_value and v2  > at_value :
					above_axis[c2]=1
				elif v2 < at_value and v1 > at_value :
					above_axis[c1]=1
			
			above('a', 'b')
			above('b', 'c')
			above('a', 'c')
			
			if len([a for a in above_axis if above_axis[a] == 1]) > 0 :
				faces.append((fk, above_axis))
				
		return faces
				
	def stop_at_axis(self, v_to, v_from, axis, value) :
		d = { 'x' : v_to.x - v_from.x, 'y' : v_to.y - v_from.y, 'z' : v_to.z - v_from.z }
		distance = value - v_from.get(axis)
		proportion = distance / d[axis]
		return Vertex(v_from.x + proportion * d['x'], v_from.y + proportion * d['y'], v_from.z + proportion * d['z'], v_from.u, v_from.v, v_from.w)
			
	def slice_at_axis_value(self, axis, value) :
	
		def add_vs(fs) :
			return (
				self.add_vertex(self.vertex[fs[1]].half_way_toward(self.vertex[fs[0]])), 
				self.add_vertex(self.stop_at_axis(self.vertex[fs[2]], self.vertex[fs[0]], axis, value)), 
				self.add_vertex(self.stop_at_axis(self.vertex[fs[2]], self.vertex[fs[1]], axis, value))
			)
			
		for (fk, above) in self.faces_crossing_axis(axis, value) :
		
			face = self.face[fk]
			
			aboves = [a for a in above if above[a] == 1]
			belows = [b for b in above if above[b] == 0]
			
			if len(aboves) == 2 :
				fs = (face.get(aboves[0]), face.get(aboves[1]), face.get(belows[0]))
			else :
				fs = (face.get(belows[0]), face.get(belows[1]), face.get(aboves[0]))
		

			vs = add_vs(fs)
			
			self.add_face(Face(vs[0], vs[1], vs[2], face.u,  face.v,  face.w))
			self.add_face(Face(fs[0], vs[0], vs[1], face.u,  face.v,  face.w))
			self.add_face(Face(fs[1], vs[0], vs[2], face.u,  face.v,  face.w))
			self.add_face(Face(fs[2], vs[1], vs[2], face.u,  face.v,  face.w))
			
			del self.face[fk]
			
			
	def project_from_plane_to_plane(self, axis, from_value, to_value, direction) :
		# plan view. project everything below from_value onto plane in to_value pointing in direction
		
		uvw = {'x':'u', 'v':'v','z':'w'}
		
		if direction < 0 :
			for vk in self.vertex :
				if self.vertex[vk].get(axis) > from_value and self.vertex[vk].get(uvw[axis]) > 0 :
					self.vertex[vk].set(axis, to_value)
			self.delete_faces_with_minus_axis_normal(uvw[axis])
			
				
		else :
			for vk in self.vertex :
				if self.vertex[vk].get(axis) > from_value  and self.vertex[vk].get(uvw[axis]) < 0 :
					self.vertex[vk].set(axis, to_value)
					
			self.delete_faces_with_positive_axis_normal(uvw[axis])
			
				
		
	
		
		
	